/* Copyright © 2015 Hein-Pieter van Braam <hp@tmm.cx>
 * This work is free. You can redistribute it and/or modify it under the
 * terms of the Do What The Fuck You Want To Public License, Version 2,
 * as published by Sam Hocevar. See the COPYING file for more details.
 *
 * Text copied from http://www.firthworks.com/roger/cloak/
*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <readline/readline.h>
#include <readline/history.h>

#define BOLD    "\033[1m"
#define NO_BOLD "\033[22m"

int room;
int new_room;
int message_disturbed;

struct noun;
typedef void (*do_verb) (struct noun * noun);

void do_n(struct noun *noun);
void do_e(struct noun *noun);
void do_s(struct noun *noun);
void do_w(struct noun *noun);

void do_look(struct noun *noun);
void do_take(struct noun *noun);
void do_drop(struct noun *noun);
void do_inventory(struct noun *noun);
void do_hang(struct noun *noun);
void do_read(struct noun *noun);

enum location_tokens {
	PLAYER,
	FOYER,
	BAR,
	CLOAKROOM
};

struct room_direction {
	int location_token;
	const char *message;
};

struct room {
	int location_token;
	const char *name;
	const char *description;
	struct room_direction north;
	struct room_direction east;
	struct room_direction south;
	struct room_direction west;
};

struct room rooms[] = {
	{
	 FOYER,
	 "Foyer of the Opera House",
	 "You are standing in a spacious hall, splendidly decorated in red and gold, with glittering chandeliers overhead.  The entrance from the street is to the north, and there are doorways south and west.",
	 {-1, "You've only just arrived, and besides, the weather outside seems to be getting worse."},
	 {-1, NULL},
	 {BAR, NULL},
	 {CLOAKROOM, NULL},
	 },
	{
	 BAR,
	 "Foyer Bar",
	 "The bar, much rougher than you'd have guessed after the opulence of the foyer to the north, is completely empty.  There seems to be some sort of message scrawled in the sawdust on the floor.",
	 {FOYER, NULL},
	 {-1, NULL},
	 {-1, NULL},
	 {-1, NULL},
	 },
	{
	 CLOAKROOM,
	 "The Cloakroom",
	 "The walls of this small room were clearly once lined with hooks, though now only one remains.  The exit is a door to the east.",
	 {-1, NULL},
	 {FOYER, NULL},
	 {-1, NULL},
	 {-1, NULL},
	 },
	{
	 0,
	 NULL,
	 NULL,
	 {0, NULL},
	 {0, NULL},
	 {0, NULL},
	 {0, NULL},
	 },
};

enum verb_tokens {
	NORTH,
	EAST,
	SOUTH,
	WEST,
	LOOK,
	EXAMINE,
	TAKE,
	DROP,
	INVENTORY,
	HANG,
	READ,
};

struct verb {
	const char *verb_string;
	int verb_token;
	do_verb function;
};

struct verb verbs[] = {
	{"n", NORTH, do_n},
	{"north", NORTH, do_n},
	{"e", EAST, do_e},
	{"east", EAST, do_e},
	{"s", SOUTH, do_s},
	{"south", SOUTH, do_s},
	{"w", WEST, do_w},
	{"west", WEST, do_w},
	{"look", LOOK, do_look},
	{"examine", LOOK, do_look},
	{"take", TAKE, do_take},
	{"drop", DROP, do_drop},
	{"i", INVENTORY, do_inventory},
	{"inventory", INVENTORY, do_inventory},
	{"hang", HANG, do_hang},
	{"read", READ, do_read},
	{NULL, 0, NULL}
};

enum noun_tokens {
	HOOK,
	CLOAK,
	MESSAGE
};

struct noun {
	int noun_token;
	int location_token;
	int takeable;
	const char *name;
	const char *description;
};

struct noun nouns[] = {
	{
	 HOOK,
	 CLOAKROOM,
	 0,
	 "hook",
	 "It's just a small brass hook"},
	{
	 CLOAK,
	 PLAYER,
	 1,
	 "cloak",
	 "A handsome cloak, of velvet trimmed with satin, and slightly splattered with raindrops.  Its blackness is so deep that it almost seems to suck light from the room."},
	{
	 MESSAGE,
	 BAR,
	 0,
	 "message",
	 "A message in the sawdust"},
	{
	 0,
	 0,
	 0,
	 NULL,
	 NULL}
};

struct room *get_room() {
	struct room *r;
	for (r = rooms; r->name; ++r) {
		if (r->location_token == room)
			break;
	}

	return r;
}

struct noun *find_noun(int noun) {
	struct noun *n;

	for (n = nouns; n->name; ++n) {
		if (n->noun_token == noun) {
			break;
		}
	}

	return n;
}

void do_look(struct noun *noun) {
	struct room *r = get_room();
	struct noun *cloak = find_noun(CLOAK);

	if ((room == BAR) && cloak->location_token == PLAYER) {
		printf("It is pitch dark, and you can't see a thing.\n");
		return;
	}

	if (noun == NULL) {
		printf("%s\n", r->description);
		return;
	}

	if ((noun->location_token == PLAYER) || noun->location_token == room) {
		printf("%s\n", noun->description);
	} else {
		printf("The %s isn't here.\n", noun->name);
	}
}

void do_inventory( __attribute__ ((unused))
		  struct noun *noun) {
	struct noun *n;
	printf("You are carrying:\n");

	for (n = nouns; n->name; ++n) {
		if (n->location_token == PLAYER) {
			printf("\t%s\n", n->name);
		}
	}
}

void do_take(struct noun *noun) {
	if (noun == NULL) {
		printf("Take what?\n");
		return;
	}

	if (noun->location_token == room) {
		if (noun->takeable) {
			printf("You took the %s.\n", noun->name);
			noun->location_token = PLAYER;
		} else {
			printf("You can't take the %s.\n", noun->name);
		}
	} else if (noun->location_token == PLAYER) {
		printf("You already have the %s.\n", noun->name);
	} else {
		printf("There's no %s here.\n", noun->name);
	}
}

void do_drop(struct noun *noun) {
	if (noun == NULL) {
		printf("Drop what?\n");
		return;
	}

	if ((noun->noun_token == CLOAK) && (room != CLOAKROOM)) {
		printf("This isn't the best place to leave a smart cloak lying around.\n");
		return;
	}

	if (noun->location_token == PLAYER) {
		printf("You dropped the %s to the floor.\n", noun->name);
		noun->location_token = room;
	} else {
		printf("You're not carrying the %s\n", noun->name);
	}
}

void do_move(int direction) {
	struct room *r = get_room();
	struct room_direction *d;

	switch (direction) {
	case NORTH:
		d = &r->north;
		break;
	case EAST:
		d = &r->east;
		break;
	case SOUTH:
		d = &r->south;
		break;
	case WEST:
		d = &r->west;
		break;
	}

	if (d->location_token > 0) {
		room = d->location_token;
		new_room = 1;
	} else {
		if (d->message) {
			printf("%s\n", d->message);
		} else {
			printf("You can't go that way.\n");
		}
	}
}

void do_hang(struct noun *noun) {
	if (noun == NULL) {
		printf("Hang what?\n");
		return;
	}

	if ((noun->noun_token == CLOAK) && (room == CLOAKROOM)) {
		printf("You hang the coat on the hook.\n");
		noun->location_token = room;
		return;
	}

	if (noun->location_token == PLAYER) {
		printf("You can't hang a %s.\n", noun->name);
	} else {
		printf("You're not carrying the %s\n", noun->name);
	}
}

void do_read(struct noun *noun) {
	if (noun == NULL) {
		printf("Read what?\n");
		return;
	}

	if ((noun->location_token == room) && noun->noun_token == MESSAGE) {
		if (message_disturbed == 0) {
			printf("The message, neatly marked in the sawdust, reads...\n");
			printf("You win!\n");
			exit(0);
		}

		if (message_disturbed == 1) {
			printf("The message, slightly scuffed, in the sawdust, reads...\n");
			printf("You win!\n");
			exit(0);
		}

		printf("The message has been carelessly trampled, making it difficult to read.  You can just distinguish the words...\n");
		printf("You have lost!\n");
		exit(0);
	}

	printf("You can't read a %s\n", noun->name);
}

void do_n( __attribute__ ((unused))
	  struct noun *noun) {
	do_move(NORTH);
}

void do_e( __attribute__ ((unused))
	  struct noun *noun) {
	do_move(EAST);
}

void do_s( __attribute__ ((unused))
	  struct noun *noun) {
	do_move(SOUTH);
}

void do_w( __attribute__ ((unused))
	  struct noun *noun) {
	do_move(WEST);
}

void parse(const char *input) {
	int found_verb = -1;
	int found_noun = -1;
	int verb_length = 0;
	int has_noun = 0;

	if (!strlen(input)) {
		return;
	}

	char *i = strstr(input, " ");
	if (i) {
		verb_length = i - input;
		has_noun = 1;
	} else {
		verb_length = strlen(input);
	}

	struct verb *v;
	for (v = verbs; v->verb_string; ++v) {
		if (strncasecmp(v->verb_string, input, verb_length) == 0) {
			found_verb = v->verb_token;
			break;
		}
	}

	struct noun *n;
	if (has_noun) {
		for (n = nouns; n->name; ++n) {
			if (strcasecmp(n->name, input + verb_length + 1) == 0) {
				found_noun = n->noun_token;
				break;
			}
		}
	}

	if (found_noun < 0) {
		n = NULL;
	}

	if (found_verb < 0) {
		printf("I don't know how to \"%s\"\n", input);
	} else {
		if ((has_noun) && (found_noun < 0)) {
			printf("You don't see a \"%s\" here\n", input + verb_length + 1);
		} else {
			struct noun *cloak = find_noun(CLOAK);
			if ((room == BAR) && (found_verb != NORTH) && cloak->location_token == PLAYER) {
				++message_disturbed;
				if (found_verb == EAST || found_verb == SOUTH || found_verb == WEST) {
					printf("Blundering around in the dark isn't a good idea!\n");
				} else {
					printf("In the dark? You could easily disturb something.\n");
				}
			} else {
				v->function(n);
			}
		}
	}
}

int main() {
	char *input;
	char *prompt = "> ";

	new_room = 1;
	room = FOYER;
	message_disturbed = 0;

	printf("Hurrying through the rainswept November night, you're glad to see the bright lights of the Opera House.  It's surprising that there aren't more people about but, hey, what do you expect in a cheap demo game...?\n");

	for (;;) {
		printf("\n");

		struct room *r = get_room();
		printf(BOLD "%s\n" NO_BOLD, r->name);

		if (new_room) {
			do_look(NULL);
			new_room = 0;
		}

		input = readline(prompt);

		if (!input)
			break;

		add_history(input);
		parse(input);

		free(input);
	}

	printf("\n");
	return 0;
}
